
#include "GLRenderer.h"

#include <cstring>
#include <cassert>

#include <GL/gl.h>
//#include <GL/glu.h>
//#include <GL/glut.h>
#include <GL/glext.h>
//#include <GL/glew.h>
//#include <GL/wglew.h>
#include <string>
#include <fstream>
#include <iostream>
#include <algorithm>

//#include <GL/glaux.h>

#include "leak.h"
//#include "globals.h"

using namespace std;


Program::Program(const char* vshader_file, const char* fshader_file) {

	/*
	GLuint temp_attributes[MAX_ATTRIBUTES];
	num_attributes_ = 0;
	GLuint temp_uniforms[MAX_UNIFORMS];
	num_uniforms_ = 0;
	*/

	string str;
	string vshader_src;
	string fshader_src;
	ifstream file(vshader_file);

	//if(!file.good())
	//{
	//	cout << "cant open: " << vshader_file << endl;
	//}
	assert(file.good());

	while(getline(file, str))
	{
		vshader_src += str;
		vshader_src.push_back('\n');
	}

	ifstream file2(fshader_file);

	while(getline(file2, str))
	{
		fshader_src += str;
		fshader_src.push_back('\n');
	}
	GLuint vertexShader = LoadShader(GL_VERTEX_SHADER, vshader_src.c_str());
    if (!vertexShader) {
        cout << "vertexShader error" << endl;
    }

    GLuint pixelShader = LoadShader(GL_FRAGMENT_SHADER, fshader_src.c_str());
    if (!pixelShader) {
        cout << "fragmentShader error" << endl;
    }

	gl_program_ = glCreateProgramObjectARB();
    if (gl_program_) {
    	cout << "q" << endl;
        glAttachShader(gl_program_, vertexShader);
        Renderer::CheckGLError("glAttachShader");
        glAttachShader(gl_program_, pixelShader);
		Renderer::CheckGLError("glAttachShader");
        glLinkProgram(gl_program_);
        GLint linkStatus = GL_FALSE;
        glGetProgramiv(gl_program_, GL_LINK_STATUS, &linkStatus);
        if (linkStatus != GL_TRUE) {
            GLint bufLength = 0;
            glGetProgramiv(gl_program_, GL_INFO_LOG_LENGTH, &bufLength);
            if (bufLength) {
                char* buf = (char*) malloc(bufLength);
                if (buf) {
                    glGetProgramInfoLog(gl_program_, bufLength, NULL, buf);
                    printf("Could not link program:\n%s\n", buf);
                    free(buf);
                }
            }
            glDeleteProgram(gl_program_);
            gl_program_ = 0;
        }
    } else
    {
    	cout << "error " << __LINE__ << endl;
    }

	vtx_attributes_[POSITION_ATTRIBUTE] = glGetAttribLocation(gl_program_, "aVertexPosition");
	vtx_attributes_[NORMAL_ATTRIBUTE] = glGetAttribLocation(gl_program_, "aVertexNormals");
	vtx_attributes_[UV_ATTRIBUTE] = glGetAttribLocation(gl_program_, "aVertexUVs");
	
	uniforms_[MVP_UNIFORM] = glGetUniformLocation(gl_program_, "uMVPMatrix");
	uniforms_[MV_UNIFORM] = glGetUniformLocation(gl_program_, "uMVMatrix");
	uniforms_[COL_UNIFORM] = glGetUniformLocation(gl_program_, "col");
	
	uniforms_[DIFFUSE_SAMPLER] = glGetUniformLocation(gl_program_, "s_texture");

	cout << "uniform: " << vtx_attributes_[UV_ATTRIBUTE] << endl;

}

Program::~Program(){
}

void Program::Use() {

	glUseProgram(gl_program_);
	Renderer::CheckGLError("glUseProgram");

	if( uniforms_[DIFFUSE_SAMPLER] != -1 )
	{
		glUniform1i(uniforms_[DIFFUSE_SAMPLER], DIFFUSEMAP_SLOT);
	}

	//g_render_state.current_program = this;

}


vector<GLuint> Renderer::textures;
vector<string> Renderer::texture_names;

map<string, weak_ptr<GLTexture>> Renderer::managed_textures;

vector<Program> Renderer::programs;
RenderState* Renderer::render_state = nullptr;

Renderer::Renderer(int w, int h) : p_draw_func_(NULL) {
	
	//GLenum err = glewInit();
	glViewport(0, 0, w, h);
	/*
	Program new_program(	"E:/VCProj/OpenGLTries3/Release/shaders/constant.vs", 
							"E:/VCProj/OpenGLTries3/Release/shaders/constant.fs");

	programs_.push_back(new_program);

	//constant_ = new Program(	"E:/VCProj/OpenGLTries3/Release/shaders/constant.vs", 
	//							"E:/VCProj/OpenGLTries3/Release/shaders/constant.fs");
	//constant_->Use();

	Program new_program2(	"E:/VCProj/OpenGLTries3/Release/shaders/screen_draw.vs", 
							"E:/VCProj/OpenGLTries3/Release/shaders/screen_draw.fs");

	programs_.push_back(new_program2);

	*/
	glEnable(GL_CULL_FACE);
	//glFrontFace​(GL_CCW​);
	//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}

Renderer::~Renderer() {
	
	glDeleteTextures(textures.size(), &textures[0]);

}

void Renderer::SetViewport(int start_x, int start_y, int x, int y) {

	glViewport(start_x, start_y, x, y);
	//glClearColor(0.0, 0.0, 0.0, 0.0);
	//glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
	glScissor(start_x, start_y, x, y);
	glEnable( GL_SCISSOR_TEST );

}


void Renderer::set_draw_zone(int start_x, int start_y, int x, int y) {

	glViewport(start_x, start_y, x, y);
	glScissor(start_x, start_y, x, y);
	glEnable( GL_SCISSOR_TEST );

}

void Renderer::set_DrawFunc(void (*func)()){

	p_draw_func_ = func;

}
/*
int Renderer::LoadTexture(const char* texture_name) {
	
	glActiveTexture(GL_TEXTURE0);

	GLuint texture_id;
	glGenTextures(1, &texture_id);
	glBindTexture(GL_TEXTURE_2D, texture_id);

	AUX_RGBImageRec* photo_image;
	photo_image = auxDIBImageLoadA(texture_name);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, photo_image->sizeX, photo_image->sizeY,0, GL_RGB, GL_UNSIGNED_BYTE,photo_image->data);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR );
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

	glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
	glGenerateMipmap(GL_TEXTURE_2D);

	delete photo_image;

	textures_.push_back(texture_id);
	return textures_.size() - 1;

}
*/

void Renderer::Draw() {

	glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
	//glClear( GL_DEPTH_BUFFER_BIT );

	glEnable(GL_DEPTH_TEST);

	programs[CONSTANT_PROGRAM].Use();
        
	p_draw_func_();

}

void Renderer::SwitchProgram(int program) {

	if( render_state->current_program != &programs[program])
	{
		programs[program].Use();
		render_state->current_program = &programs[program];
	}

}


 void Renderer::CheckGLError(const char* op) {
    for (GLint error = glGetError(); error; error
            = glGetError()) {
		//cout << "after " << op << "() glError (0x" << error << ")" << endl; 
        printf("after %s() glError (0x%x)\n", op, error);
    }
}

 void Renderer::Init( RenderState* r_state ) {


	render_state = r_state;

	Program new_program(	"shaders/constant.vs", 
							"shaders/constant.fs");

	programs.push_back(new_program);


	Program new_program2(	"shaders/screen_draw.vs", 
							"shaders/screen_draw.fs");

	programs.push_back(new_program2);

	programs.push_back( Program( "shaders/main.vs", "shaders/main.fs") );

	render_state->current_program = &programs[CONSTANT_PROGRAM];

 }

GLuint LoadShader(GLenum shaderType, const char* pSource) {
    GLuint shader = glCreateShader(shaderType);
	//GLuint shader = glCreateShaderObjectARB(shaderType);
    if (shader) {
        glShaderSourceARB(shader, 1, &pSource, NULL);
        glCompileShader(shader);
        GLint compiled = 0;
        glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
        if (!compiled) {
            GLint infoLen = 0;
            glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
            if (infoLen) {
                char* buf = (char*) malloc(infoLen);
                if (buf) {
                    glGetShaderInfoLog(shader, infoLen, NULL, buf);
					cout << "Could not compile shader " << shaderType << ":" << endl << buf << endl;
					//printf("Could not compile shader %d:\n%s\n", shaderType, buf);
                    free(buf);
                }
                glDeleteShader(shader);
                shader = 0;
            }
        }
    } else
    {
    	cout << "error\n";
    }
    return shader;
}

int Renderer::LoadTexture(const char* texture_name) {
	
	/*for( auto name = texture_names.begin(); name != texture_names.end(); name++ )
	{
		if( strcmp( texture_name, (*name).c_str() ) == 0)
		{
			return ( name - texture_names.begin() );
		}
	}*/
	auto name = find(texture_names.begin(), texture_names.end(), texture_name);
	if(name != texture_names.end())
	{
		return (name - texture_names.begin());
	}

	/*int len = strlen(texture_name);
	char ext[4];
	ext[0] = texture_name[len-1-2];
	ext[1] = texture_name[len-1-1];
	ext[2] = texture_name[len-1];
	ext[3] = '\0';*/

	int texture_id = 0;

	//if(strcmp(ext, "tga") == 0 || strcmp(ext, "TGA") == 0)
	if(strstr(texture_name, ".tga") != 0 || strstr(texture_name, ".TGA") != 0)
	{
		//cout << "ttt: " << texture_name << endl;
		texture_id =  LoadTGATexture(texture_name);
	}

	textures.push_back(texture_id);
	//string s(texture_name);
	texture_names.push_back(texture_name);
	return textures.size() - 1;
}

shared_ptr<GLTexture> Renderer::LoadTexture2(const char* texture_name)
{
	map<string, weak_ptr<GLTexture>>::iterator val = managed_textures.find(texture_name);
	if(val != managed_textures.end())
	{
		if(!(*val).second.expired())
		{
			return (*val).second.lock();
		}
	}

	int texture_id = 0;
	if(strstr(texture_name, ".tga") != 0 || strstr(texture_name, ".TGA") != 0)
	{
		texture_id = LoadTGATexture(texture_name);
	}
	std::shared_ptr<GLTexture> ret_val(make_shared<GLTexture>(texture_id));
	managed_textures[texture_name] = std::weak_ptr<GLTexture>(ret_val);
	return ret_val;
}

int Renderer::LoadTGATexture(const char* texture_name) {

	glActiveTexture(GL_TEXTURE0);

	GLuint texture_id;
	glGenTextures(1, &texture_id);
	glBindTexture(GL_TEXTURE_2D, texture_id);
	glEnable(GL_TEXTURE_2D);
	int Width, Height;

	GLubyte TGAheader[12]={0,0,2,0,0,0,0,0,0,0,0,0};                        // Uncompressed TGA Header
    GLubyte TGAcompare[12];                                                 // Used To Compare TGA Header
    GLubyte header[6];                                                      // First 6 Useful Bytes From The Header
    GLuint  bytesPerPixel;                                                  // Holds Number Of Bytes Per Pixel Used In The TGA File
    GLuint  imageSize;                                                      // Used To Store The Image Size When Setting Aside Ram
    GLuint  temp;                                                           // Temporary Variable
    //GLuint  type=GL_RGBA;                                                   // Set The Default GL Mode To RBGA (32 BPP)
	//char Path[MAX_PATH];
    FILE *file = fopen(texture_name, "rb");                                      // Open The TGA File
    //FILE *file;
	//fopen( &file, texture_name, "rb" );
	if( file==NULL ||                                                       // Does File Even Exist?
    fread(TGAcompare,1,sizeof(TGAcompare),file)!=sizeof(TGAcompare) ||      // Are There 12 Bytes To Read?
    memcmp(TGAheader,TGAcompare,sizeof(TGAheader))!=0               ||      // Does The Header Match What We Want?
    fread(header,1,sizeof(header),file)!=sizeof(header))                    // If So Read Next 6 Header Bytes
    {
        if (file == NULL)                                                   // Did The File Even Exist? *Added Jim Strong*
        {
            //MessageBox(g_hMainWindow,"Image file was not found","Error",MB_OK|MB_ICONERROR);
			cout << "Image file was not found (" << texture_name << ")" << endl;
            return false;                                                   // Return False
        }
            if (TGAcompare[2] == 1)
                //MessageBox(g_hMainWindow,"Image cannot be indexed color. \r\n Convert the image to RGB or RGBA.","Error",MB_OK|MB_ICONERROR);
				cout << "Image cannot be indexed color." << endl;
            if (TGAcompare[2] == 3)
               // MessageBox(g_hMainWindow,"Image cannot be greyscale color. \r\n Convert the image to RGB or RGBA.","Error",MB_OK|MB_ICONERROR);
				cout << "Image cannot be greyscale color." << endl;
			if (TGAcompare[2] == 9 || TGAcompare[2] == 10)
                //MessageBox(g_hMainWindow,"Image cannot be compressed. \r\n Convert the image to an uncompressed format.","Error",MB_OK|MB_ICONERROR);
				cout << "Image cannot be compressed." << endl;
			fclose(file);                                                   // If Anything Failed, Close The File
            return false;                                                   // Return False
        }
        Width  = header[1] * 256 + header[0];                      // Determine The TGA Width      (highbyte*256+lowbyte)
        Height = header[3] * 256 + header[2];                      // Determine The TGA Height     (highbyte*256+lowbyte)
        if ( Width  <=0     ||                                      // Is The Width Less Than Or Equal To Zero
        Height <=0     ||                                          // Is The Height Less Than Or Equal To Zero
        (header[4]!=24 && header[4]!=32))                                   // Is The TGA 24 or 32 Bit?
        {
            fclose(file);                                                   // If Anything Failed, Close The File
            if (Width  <=0 || Height  <=0)
                //MessageBox(g_hMainWindow,"Image must have a width and height greater than 0","Error",MB_OK|MB_ICONERROR);
				cout << "Image must have a width and height greater than 0" << endl;
            if (header[4]!=24 && header[4]!=32)
                //MessageBox(g_hMainWindow,"Image must be 24 or 32 bit","Error",MB_OK|MB_ICONERROR);
				cout << "Image must be 24 or 32 bit" << endl;
            return false;                                               // Return False
        }
        GLubyte Bpp    = header[4];                                        // Grab The TGA's Bits Per Pixel (24 or 32)
        bytesPerPixel   = Bpp/8;                                   // Divide By 8 To Get The Bytes Per Pixel
        imageSize       = Width*Height*bytesPerPixel;     // Calculate The Memory Required For The TGA Data
        GLubyte* ImageData = (GLubyte *)malloc(imageSize);                    // Reserve Memory To Hold The TGA Data
        if (ImageData == NULL ||                                     // Does The Storage Memory Exist?
        fread(ImageData, 1, imageSize, file)!=imageSize)           // Does The Image Size Match The Memory Reserved?
        {
            if (ImageData!=NULL)                                    // Was Image Data Loaded
                free(ImageData);                                   // If So, Release The Image Data
            //MessageBox(g_hMainWindow,"Image load failed for unknown reason.","Error",MB_OK|MB_ICONERROR);
			cout << "Image load failed for unknown reason." << endl;
            fclose(file);                                                   // Close The File
            return false;                                                   // Return False
        }
        for (GLuint i=0; i<(GLuint)imageSize; i+=bytesPerPixel)                 // Loop Through The Image Data
        {                                                                   // Swaps The 1st And 3rd Bytes ('R'ed and 'B'lue)
            temp = ImageData[i];                                     // Temporarily Store The Value At Image Data 'i'
            ImageData[i] = ImageData[i + 2];                       // Set The 1st Byte To The Value Of The 3rd Byte
            ImageData[i + 2] = (GLubyte)temp;                               // Set The 3rd Byte To The Value In 'temp' (1st Byte Value)
        }
        fclose(file);

		Renderer::CheckGLError( "11" );

	
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );

	//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST );
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );

	if( Bpp == 32 )
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, Width, Height,0, GL_RGBA, GL_UNSIGNED_BYTE, ImageData);
	else
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, Width, Height,0, GL_RGB, GL_UNSIGNED_BYTE, ImageData);


	static GLfloat fLargest = -1.0f;
	if( fLargest == -1.0f )
		glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest);

	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest);

	glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
	glGenerateMipmap( GL_TEXTURE_2D );
	Renderer::CheckGLError( "glTexImage2D" );
	
	Renderer::CheckGLError( "glTexParameteri" );

	//glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
	//glGenerateMipmap( GL_TEXTURE_2D );

	Renderer::CheckGLError( "glGenerateMipmap" );

	//delete photo_image;
	free(ImageData);

	return texture_id;

}

string Renderer::GetTextureName(shared_ptr<GLTexture> tex)
{
	for(auto tex_it : managed_textures)
	{
		if(tex_it.second.lock() == tex)
		{
			return tex_it.first;
		}
	}
	return "missing_texture.tga";
}

int Renderer::GetTextureIndex(shared_ptr<GLTexture> tex)
{
	for(auto tex_it = managed_textures.begin(); tex_it != managed_textures.end(); tex_it++)
	{
		if((*tex_it).second.lock() == tex)
		{
			return distance(managed_textures.begin(), tex_it);
		}
	}
	return 0;
}

void Renderer::DeleteTexture(unsigned int tex_index) {
	glDeleteTextures(1, &textures[tex_index]);
	cout << "sizes: " << textures.size() << " " << texture_names.size() << endl;
	textures.erase(textures.begin() + tex_index);
	texture_names.erase(texture_names.begin() + tex_index);
}

void Renderer::DeleteOwnTextures(unsigned int num_textures, unsigned int* tex_indexes) {
	glDeleteTextures(num_textures, tex_indexes);
}

void Renderer::SwitchTexture(int slot, int tex)
{

	/*if( render_state->binded_textures[slot] != tex_index )
	{
		glActiveTexture( GL_TEXTURE0 + slot );
		glBindTexture( GL_TEXTURE_2D, textures[tex_index] );
		render_state->binded_textures[slot] = tex_index;
	}*/

	if(render_state->binded_textures[slot] != tex)
	{
		glActiveTexture(GL_TEXTURE0 + slot);
		glBindTexture(GL_TEXTURE_2D, tex);
		render_state->binded_textures[slot] = tex;
	}
}


void Renderer::SwitchOwnTexture(int slot, int tex) {

	glActiveTexture( GL_TEXTURE0 + slot );
	glBindTexture( GL_TEXTURE_2D, tex );
	render_state->binded_textures[slot] = -1;

}


/*
void Renderer::EnableOpenGL (HWND hWnd, HDC *hDC, HGLRC *hRC)
{
    PIXELFORMATDESCRIPTOR pfd;
    int iFormat;

    *hDC = GetDC (hWnd);

    ZeroMemory (&pfd, sizeof (pfd));
    pfd.nSize = sizeof (pfd);
    pfd.nVersion = 1;
    pfd.dwFlags = PFD_DRAW_TO_WINDOW | 
      PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = 32;
    pfd.cDepthBits = 32;
	pfd.cAlphaBits = 8;
    pfd.iLayerType = PFD_MAIN_PLANE;
    iFormat = ChoosePixelFormat (*hDC, &pfd);
    SetPixelFormat (*hDC, iFormat, &pfd);

	static bool need_new_rc = true;
	static HGLRC s_hRC;

	HGLRC tempContext = wglCreateContext(*hDC);
        wglMakeCurrent(*hDC, tempContext);

	GLenum err = glewInit();
        if (GLEW_OK != err)
        {
                printf("GLEW is not initialized!");
        }
 
        int attribs[] =
        {
                WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
                WGL_CONTEXT_MINOR_VERSION_ARB, 1,
                WGL_CONTEXT_FLAGS_ARB, 0,
                0
        };
 
        if(wglewIsSupported("WGL_ARB_create_context") == 1)
        {
				if( need_new_rc )
				{
                *hRC = wglCreateContextAttribsARB(*hDC,0, attribs);
				s_hRC = *hRC;
				need_new_rc = false;
				} else
				{
					*hRC = s_hRC;
				}
                wglMakeCurrent(NULL,NULL);
                wglDeleteContext(tempContext);
                wglMakeCurrent(*hDC, *hRC);
        }
        else
        {       //It's not possible to make a GL 3.x context. Use the old style context (GL 2.1 and before)
                *hRC = tempContext;
        }

  

}

void Renderer::SetVSync(bool sync)
{	
	// Function pointer for the wgl extention function we need to enable/disable
	// vsync
	typedef BOOL (APIENTRY *PFNWGLSWAPINTERVALPROC)( int );
	PFNWGLSWAPINTERVALPROC wglSwapIntervalEXT = 0;

	const char *extensions = (char*)glGetString( GL_EXTENSIONS );

	if( strstr( extensions, "WGL_EXT_swap_control" ) == 0 )
	{
		return;
	}
	else
	{
		wglSwapIntervalEXT = (PFNWGLSWAPINTERVALPROC)wglGetProcAddress( "wglSwapIntervalEXT" );

		if( wglSwapIntervalEXT )
			wglSwapIntervalEXT(sync);
	}
}

void Renderer::DisableOpenGL (HWND hWnd, HDC hDC, HGLRC hRC)
{
    wglMakeCurrent (NULL, NULL);
    wglDeleteContext (hRC);
    ReleaseDC (hWnd, hDC);
}
*/
